package com.rocky.component.elasticsearch.annotation.scanner;

import com.rocky.component.elasticsearch.annotation.RockyESDate;
import com.rocky.component.elasticsearch.annotation.RockyESGeoPoint;
import com.rocky.component.elasticsearch.annotation.RockyESModel;
import com.rocky.component.elasticsearch.config.property.RockyElasticsearchProperty;
import com.rocky.component.elasticsearch.enums.RockyESPropertyEnum;
import com.rocky.component.elasticsearch.enums.RockyESTypeEnum;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author rocky
 * date 2025-08-05
 * Description:
 */
@RequiredArgsConstructor
public class RockyESModelScanner {

    private final Logger logger = LoggerFactory.getLogger(RockyESModelScanner.class);

    private final RockyElasticsearchProperty elasticsearchProperty;

    private final RestHighLevelClient restHighLevelClient;

    private static final Integer MIN_NUMBER = 1;

    @PostConstruct
    void init() throws ClassNotFoundException, IOException {
        ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false);
        componentProvider.addIncludeFilter(new AnnotationTypeFilter(RockyESModel.class));

        Set<BeanDefinition> candidateComponents = componentProvider.findCandidateComponents(elasticsearchProperty.getBasePackage());
        for (BeanDefinition candidateComponent : candidateComponents) {
            // RockyESModel 注解标注的 POJO
            Class<?> aClass = Class.forName(candidateComponent.getBeanClassName());
            // RockyESModel 注解信息
            RockyESModel rockyESModel = aClass.getAnnotation(RockyESModel.class);

            // 构建索引名称
            String index = elasticsearchProperty.getIndex().getIndexPrefixName() + rockyESModel.indexName();
            if (StringUtils.isNotBlank(rockyESModel.indexNamePrefix())) {
                index = rockyESModel.indexNamePrefix() + rockyESModel.indexName();
            }
            // 构建索引映射
            Map<String, Object> sourceMapping = buildMapping(aClass);

            // 创建索引
            createIndex(rockyESModel, index, sourceMapping);
        }
    }

    private Map<String, Object> buildMapping(Class<?> aClass) {
        Map<String, Object> properties = new HashMap<>();
        while (Object.class != aClass) {
            for (Field declaredField : aClass.getDeclaredFields()) {
                // 转换 ES 数据类型
                String esType = covertToESType(declaredField);
                // 构建映射字段属性
                properties.put(declaredField.getName(), buildMessage(declaredField, esType));
            }

            aClass = aClass.getSuperclass();
        }

        Map<String, Object> mapping = new HashMap<>();
        mapping.put(RockyESPropertyEnum.PROPERTIES.getCode(), properties);
        return mapping;
    }

    private String covertToESType(Field field) {
        Annotation[] annotations = field.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(RockyESDate.class)) {
                return RockyESTypeEnum.DATE.getCode();
            }
            if (annotation.annotationType().equals(RockyESGeoPoint.class)) {
                return RockyESTypeEnum.GEO_POINT.getCode();
            }
        }

        Class<?> fieldType = field.getType();
        // 处理集合，返回的结果以集合的泛型为准
        if (Collection.class.isAssignableFrom(fieldType)) {
            Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    fieldType = (Class<?>) actualTypeArgument;
                }
            }
        }

        if (fieldType.equals(Byte.class) || fieldType.equals(Short.class) || fieldType.equals(Integer.class) || fieldType.equals(Long.class) ||
                fieldType.equals(byte.class) || fieldType.equals(short.class) || fieldType.equals(int.class) || fieldType.equals(long.class)) {
            return RockyESTypeEnum.LONG.getCode();
        }
        if (fieldType.equals(Float.class) || fieldType.equals(Double.class) || fieldType.equals(float.class) || fieldType.equals(double.class)) {
            return RockyESTypeEnum.FLOAT.getCode();
        }
        if (fieldType.equals(Character.class) || fieldType.equals(char.class)) {
            return RockyESTypeEnum.TEXT.getCode();
        }
        if (fieldType.equals(Boolean.class) || fieldType.equals(boolean.class)) {
            return RockyESTypeEnum.BOOLEAN.getCode();
        }

        if (fieldType.equals(BigDecimal.class)) {
            return RockyESTypeEnum.LONG.getCode();
        }
        if (fieldType.equals(String.class)) {
            return RockyESTypeEnum.TEXT.getCode();
        }
        if (fieldType.equals(LocalDate.class) || fieldType.equals(LocalDateTime.class)) {
            return RockyESTypeEnum.DATE.getCode();
        }

        throw new RuntimeException("Rocky elasticsearch model scanner index created error, [" + fieldType.getSimpleName() + "] data type not currently supported");
    }

    private Map<String, Object> buildMessage(Field field, String esType) {
        Map<String, Object> message = new HashMap<>();
        message.put(RockyESPropertyEnum.TYPE.getCode(), esType);

        /** 对应的数据结构类型，如下：
         * {
         *   "type" : "text",
         *   "fields" : {
         *     "keyword" : {
         *       "type" : "keyword",
         *       "ignore_above" : 256
         *     }
         *   }
         * }
         */
        if (RockyESTypeEnum.TEXT.getCode().equals(esType)) {
            HashMap<String, Object> keyword = new HashMap<>();
            keyword.put(RockyESPropertyEnum.TYPE.getCode(), RockyESTypeEnum.KEYWORD.getCode());
            keyword.put(RockyESPropertyEnum.IGNORE_ABOVE.getCode(), 256);

            HashMap<String, Object> fields = new HashMap<>();
            fields.put(RockyESPropertyEnum.KEYWORD.getCode(), keyword);

            message.put(RockyESPropertyEnum.FIELDS.getCode(), fields);
        }

        /** 对应的数据结构类型，如下：
         * {
         *   "type" : "date",
         *   "format" : "yyyy-MM-dd"
         * }
         */
        if (RockyESTypeEnum.DATE.getCode().equals(esType)) {
            if (field.isAnnotationPresent(RockyESDate.class)) {
                RockyESDate rockyESDate = field.getAnnotation(RockyESDate.class);
                message.put(RockyESPropertyEnum.FORMAT.getCode(), rockyESDate.format());
            }
        }

        return message;
    }

    private void createIndex(RockyESModel rockyESModel, String index, Map<String, Object> sourceMapping) throws IOException {
        // 判断索引是否存在
        GetIndexRequest getIndexRequest = new GetIndexRequest(index).humanReadable(true);
        boolean exists = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
        if (exists) {
            logger.info("Rocky elasticsearch model scanner index [" + index + "] already existed");
            return;
        }

        // 构建索引配置参数
        int numberOfShards = rockyESModel.numberOfShards() > 1 ? rockyESModel.numberOfShards() : elasticsearchProperty.getIndex().getNumberOfShards();
        int numberOfReplicas = rockyESModel.numberOfReplicas() > 1 ? rockyESModel.numberOfReplicas() : elasticsearchProperty.getIndex().getNumberOfReplicas();
        // 创建索引
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(index).mapping(sourceMapping)
                .settings(Settings.builder()
                        .put("index.number_of_shards", Math.max(numberOfShards, MIN_NUMBER))
                        .put("index.number_of_replicas", Math.max(numberOfReplicas, MIN_NUMBER))
                );
        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
        if (createIndexResponse.isAcknowledged()) {
            logger.info("Rocky elasticsearch model scanner index [" + index + "] created succeed");
        } else {
            throw new RuntimeException("Rocky elasticsearch model scanner index [" + index + "] created error");
        }
    }

}
